using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Comms;
using Engine;
using Game;

namespace RuthlessConquest {
    public class ServerGame {
        public static int NextGameId;
        public Server Server;
        public double NextGameStateRequestTime;
        public int NextGameStateRequestPlayer;
        public double NextGameImageRequestTime;
        public double NextPlayerLocationsSendTime;
        public int NextGameImageRequestPlayer;
        public bool PlayersChanged = true;
        public double LastActivityTime;
        public DynamicArray<TickMessage> TickMessages = new();
        public DynamicArray<JoinGameMessage> JoinGameMessages = new();
        public SortedDictionary<int, uint> StateHashes = new();
        public SortedDictionary<int, object> States = new();
        public int GameId;
        public const string fName = "RCServerGame";
        public GameCreationParameters CreationParameters { get; }
        public DynamicArray<ServerHumanPlayer> Players { get; } = new();
        public int Tick { get; set; }
        public int GameStartTick { get; set; } = -1;
        public GameImage GameImage { get; set; }
        public bool DesyncDetected { get; set; }

        public ServerGame(Server server, CreateGameMessage message, PeerData peerData) {
            GameId = NextGameId++;
            Server = server;
            LastActivityTime = Time.RealTime;
            CreationParameters = message.CreationParameters;
            Players.Add(
                new ServerHumanPlayer(
                    this,
                    true,
                    CreationParameters.CreatingPlayerFaction,
                    CreationParameters.CreatingPlayerName,
                    CreationParameters.CreatingPlayerGuid,
                    CreationParameters.CreatingPlayerPlatform,
                    peerData,
                    message.ReceivedVersion
                )
            );
        }

        public void Run() {
            double time = Time.RealTime;
            double num = time - LastActivityTime;
            if (num > 600.0) {
                Log.Information(string.Format(LanguageControl.Get(fName, "1"), GameId, Players.Count, num.ToString("0")));
                EndGame();
                return;
            }
            TickMessage tickMessage = new();
            tickMessage.Tick = Tick;
            tickMessage.IsGameStarted = GameStartTick >= 0;
            if (PlayersChanged) {
                PlayersChanged = false;
                foreach (ServerHumanPlayer player in Players) {
                    tickMessage.PlayerDescriptions.Add(
                        new PlayerDescription { Faction = player.Faction, Name = player.Name, Guid = player.Guid, Platform = player.Platform }
                    );
                }
                foreach (Faction faction in CreationParameters.Factions) {
                    if (!tickMessage.PlayerDescriptions.Any(player => player.Faction == faction)) {
                        tickMessage.PlayerDescriptions.Count(pd => pd.Type == CreationParameters.AILevel);
                        PlayerType playerType = CreationParameters.AILevel;
                        if (playerType > PlayerType.NoviceAI
                            && (int)faction % 2 == 0) {
                            playerType--;
                        }
                        tickMessage.PlayerDescriptions.Add(
                            new PlayerDescription {
                                Faction = faction,
                                Type = playerType,
                                Name = string.Format(LanguageControl.Get(fName, "2"), Player.GetPlayerTypeName(playerType)),
                                Guid = new Guid(
                                    (int)faction,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0,
                                    0
                                )
                            }
                        );
                    }
                }
            }
            foreach (ServerHumanPlayer player2 in Players) {
                if (player2.Orders.Count > 0) {
                    tickMessage.OrdersByFaction.Add(player2.Faction, player2.Orders.ToDynamicArray());
                    player2.Orders.Clear();
                }
            }
            foreach (ServerHumanPlayer player3 in Players) {
                Server.Peer.SendDataMessage(player3.PeerData, DeliveryMode.ReliableSequenced, Message.Write(tickMessage));
            }
            TickMessages.Add(tickMessage);
            TickMessages.RemoveAll(m => Tick > m.Tick + 30);
            if (JoinGameMessages.Count > 0
                && Players.Count > 0) {
                if (time >= NextGameStateRequestTime) {
                    ServerHumanPlayer serverHumanPlayer = Players[NextGameStateRequestPlayer++ % Players.Count];
                    Server.Peer.SendDataMessage(
                        serverHumanPlayer.PeerData,
                        DeliveryMode.ReliableSequenced,
                        Message.Write(new RequestGameStateMessage())
                    );
                    NextGameStateRequestTime = time + 1.0;
                }
                int num2 = JoinGameMessages.RemoveAll(m => time > m.ReceiveTime + Server.Peer.Settings.ConnectTimeOut);
                if (num2 > 0) {
                    Log.Warning(string.Format(LanguageControl.Get(fName, "3"), num2, GameId));
                }
            }
            if (time >= NextGameImageRequestTime
                && Players.Count > 0) {
                ServerHumanPlayer serverHumanPlayer2 = Players[NextGameImageRequestPlayer++ % Players.Count];
                Server.Peer.SendDataMessage(serverHumanPlayer2.PeerData, DeliveryMode.Reliable, Message.Write(new RequestGameImageMessage()));
                NextGameImageRequestTime = time + 5.0;
            }
            if (time >= NextPlayerLocationsSendTime
                && Players.Count > 0) {
                NextPlayerLocationsSendTime = time + 5.0;
                Z_PlayerLocationsMessage z_PlayerLocationsMessage = new();
                foreach (ServerHumanPlayer player4 in Players) {
                    z_PlayerLocationsMessage.PlayerAddresses[player4.Guid] = player4.PeerData.Address.Address.ToString();
                    z_PlayerLocationsMessage.PlayerLocations[player4.Guid] = IpLocationManager.GetCountryCode(player4.PeerData.Address.Address);
                }
                foreach (ServerHumanPlayer player5 in Players) {
                    Server.Peer.SendDataMessage(player5.PeerData, DeliveryMode.ReliableSequenced, Message.Write(z_PlayerLocationsMessage));
                }
            }
            int tick = Tick + 1;
            Tick = tick;
        }

        public void Handle(JoinGameMessage message, PeerData peerData) {
            message.Sender = peerData;
            message.ReceiveTime = Time.RealTime;
            JoinGameMessages.Add(message);
            NextGameStateRequestTime = 0.0;
        }

        public void Handle(StartGameMessage message, ServerHumanPlayer player) {
            if (player.IsGameCreator) {
                if (GameStartTick < 0) {
                    GameStartTick = Tick;
                    Log.Information(string.Format(LanguageControl.Get(fName, "4"), player.Game.GameId));
                    return;
                }
                throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "5"), player.PeerData.Address));
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "6"), player.PeerData.Address));
        }

        public void Handle(PlayerOrdersMessage message, ServerHumanPlayer player) {
            if (Tick >= CreationParameters.CountdownTicksCount) {
                player.Orders.AddRange(message.Orders);
                LastActivityTime = Time.RealTime;
                return;
            }
            throw new ProtocolViolationException(string.Format(LanguageControl.Get(fName, "7"), Tick, player.PeerData.Address));
        }

        public void Handle(GameImageMessage message, ServerHumanPlayer player) {
            if (GameImage == null
                || message.GameImage.StepIndex > GameImage.StepIndex) {
                GameImage = message.GameImage;
            }
        }

        public void Handle(GameStateMessage message, ServerHumanPlayer player) {
            JoinGameMessage[] array = JoinGameMessages.ToArray();
            foreach (JoinGameMessage message2 in array) {
                ProcessJoinGameMessage(message2, message.GameState, message.NonDefeatedFactions);
            }
        }

        public void Handle(GameStateHashMessage message, ServerHumanPlayer player) {
            if (player.Game.StateHashes.TryGetValue(message.StepIndex, out uint value)) {
                if (message.StateHash != value) {
                    DesyncDetected = true;
                    Log.Warning(string.Format(LanguageControl.Get(fName, "8"), player.Game.GameId, message.StepIndex, message.StateHash, value));
                }
            }
            else {
                player.Game.StateHashes.Add(message.StepIndex, message.StateHash);
            }
        }

        public void HandleDisconnect(ServerHumanPlayer player) {
            Log.Information(
                string.Format(LanguageControl.Get(fName, "9"), player.Name, player.PeerData.Address, player.Platform, player.Version, GameId)
            );
            PlayersChanged = true;
            LastActivityTime = Time.RealTime;
            Players.Remove(player);
            player.PeerData.Tag = null;
            if (Players.Count == 0) {
                EndGame();
            }
        }

        public void EndGame() {
            ServerHumanPlayer[] array = Players.ToArray();
            foreach (ServerHumanPlayer serverHumanPlayer in array) {
                Server.Peer.DisconnectPeer(serverHumanPlayer.PeerData);
            }
            Server.Games.Remove(this);
            Log.Information(string.Format(LanguageControl.Get(fName, "10"), GameId, Server.GetStatsString()));
        }

        public void ProcessJoinGameMessage(JoinGameMessage message, GameState gameState, DynamicArray<Faction> nonDefeatedFactions) {
            if (!Players.Any(player => player.Guid == message.PlayerGuid)) {
                int minimumTick = (gameState.StepIndex + 30 - 1) / 30;
                DynamicArray<TickMessage> dynamicArray = TickMessages.Where(m => m.Tick >= minimumTick).ToDynamicArray();
                if (gameState.StepIndex > 0
                    && (dynamicArray.Count == 0 || dynamicArray[0].Tick != minimumTick)) {
                    Log.Warning(LanguageControl.Get(fName, "11"));
                    return;
                }
                Faction faction = FindFactionForPlayer(message.PreferredFaction, nonDefeatedFactions);
                nonDefeatedFactions.Remove(faction);
                ServerHumanPlayer item = new(
                    this,
                    false,
                    faction,
                    message.PlayerName,
                    message.PlayerGuid,
                    message.PlayerPlatform,
                    message.Sender,
                    message.ReceivedVersion
                );
                Players.Add(item);
                PlayersChanged = true;
                LastActivityTime = Time.RealTime;
                Server.Peer.AcceptConnect(
                    message.Sender,
                    Message.Write(new GameJoinedMessage { GameState = gameState, TickMessages = dynamicArray })
                );
                Log.Information(
                    string.Format(
                        LanguageControl.Get(fName, "12"),
                        message.PlayerName,
                        message.Sender.Address,
                        message.PlayerPlatform,
                        message.ReceivedVersion,
                        GameId,
                        faction,
                        Tick,
                        gameState.StepIndex,
                        gameState.CalculateSize()
                    )
                );
            }
            else {
                Server.Peer.RefuseConnect(message.Sender, Message.Write(new RefusedMessage { Reason = "Player is already connected to this game" }));
            }
            JoinGameMessages.Remove(message);
        }

        public Faction FindFactionForPlayer(Faction preferredFaction, DynamicArray<Faction> nonDefeatedFactions) {
            if (preferredFaction == Faction.None) {
                return Faction.None;
            }
            foreach (Faction faction in nonDefeatedFactions) {
                if (!Players.Any(player => faction == player.Faction)) {
                    return faction;
                }
            }
            return Faction.None;
        }
    }
}